import EventDispatcher from "../event/EventDispatcher";
import HotUpdateEventName from "./HotUpdateEventName";
import HotUpdateInterface from "./HotUpdateInterface";

export default  class HotUpdateManager extends EventDispatcher {


 
    protected customManifestStr = null;


    protected manifestUrl: cc.Asset = null;

    // protected _updating: boolean =  false;

    // protected _canRetry: boolean = false;

    protected _storagePath: string = ''

    protected _localFile: string = '';

    protected _failCount: number = 0;

    protected observer:HotUpdateInterface;


    protected _am: any= null;

    setManifest(asset:cc.Asset){
        this.manifestUrl = asset;
    }


    getStoragePath():string{
        return this._storagePath;
    }

    versionCompareHandle(versionA, versionB) {
        cc.log("JS Custom Version Compare: version A is " + versionA + ', version B is ' + versionB);
        var vA = versionA.split('.');
        var vB = versionB.split('.');
        for (var i = 0; i < vA.length; ++i) {
            var a = parseInt(vA[i]);
            var b = parseInt(vB[i] || 0);
            if (a === b) {
                continue;
            }
            else {
                return a - b;
            }
        }
        if (vB.length > vA.length) {
            return -1;
        }
        else {
            return 0;
        }
    }


    // use this for initialization
    constructor(url:string,moduleName:string,observer:HotUpdateInterface) {
        super();
        this.observer = observer;
        // Hot update is only available in Native build
        // if (!cc.sys.isNative) {
        //     return;
        // }
        this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + moduleName);
        cc.log('Storage path for remote asset : ' + this._storagePath);

        var UIRLFILE = url + moduleName;
        this.customManifestStr = JSON.stringify({
            "packageUrl": UIRLFILE,
            "remoteManifestUrl": UIRLFILE + HotUpdateEventName.PRJ_NAME,
            "remoteVersionUrl": UIRLFILE + HotUpdateEventName.VER_NAME,
            "version": "0.0.1",
            "assets": {},
            "searchPaths": []
        });        

        this._localFile = this._storagePath + HotUpdateEventName.PRJ_NAME;

        cc.log(" _localFile === "+this._localFile);
        // Setup your own version compare handler, versionA and B is versions in string
        // if the return value greater than 0, versionA is greater than B,
        // if the return value equals 0, versionA equals to B,
        // if the return value smaller than 0, versionA is smaller than B.


        // Init with empty manifest url for testing custom manifest
        this._am = new jsb.AssetsManager('', this._storagePath, this.versionCompareHandle);

        // var panel = this.panel;
        // Setup the verification callback, but we don't have md5 check function yet, so only print some message
        // Return true if the verification passed, otherwise return false
        this._am.setVerifyCallback(function (path, asset) {
            // When asset is compressed, we don't need to check its md5, because zip file have been deleted.
            var compressed = asset.compressed;
            // Retrieve the correct md5 value.
            var expectedMD5 = asset.md5;
            // asset.path is relative path and path is absolute.
            var relativePath = asset.path;
            // The size of asset file, but this value could be absent.
            var size = asset.size;
            if (compressed) {
                cc.log("Verification passed : " + relativePath);
                return true;
            }
            else {
                cc.log("Verification passed : " + relativePath + ' (' + expectedMD5 + ')');
                return true;
            }
        });

        cc.log('Hot update is ready, please check or directly update.');

        if (cc.sys.os === cc.sys.OS_ANDROID) {
            // Some Android device may slow down the download process when concurrent tasks is too much.
            // The value may not be accurate, please do more test and find what's most suitable for your game.
            this._am.setMaxConcurrentTask(2);
            cc.log("Max concurrent tasks count have been limited to 2");
        }

    }

    onDestroy() {
        // if (this._updateListener) {
            this._am.setEventCallback(null);
            // this._updateListener = null;
        // }
    }

    /**
     * 检查是否有更新的回调函数
     * @param event 
     */
    private checkCb(event) {
        cc.log('Code: ' + event.getEventCode());
        switch (event.getEventCode())
        {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                cc.log("No local manifest file found, hot update skipped.");
                this.observer.onLoadFileFail();
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                cc.log("Fail to download manifest file, hot update skipped.");
                this.observer.onLoadFileFail();
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                cc.log("Already up to date with the latest remote version.");
                this.publish(HotUpdateEventName.UPDATE_FINISH,"update finish");
                this.observer.onUpdateFinish();
                break;
            case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                cc.log('New version found, please try to update.');
                this.observer.onNeedUpdate();
                break;
            default:
                return;
        }
        
        // this.onDestroy();
        // this._checkListener = null;
        // this._updating = false;
    }  

    
    private updateCb(event) {
        var needRestart = false;
        var failed = false;
        cc.log("updateCb event.getEventCode()  "+event.getEventCode())
        switch (event.getEventCode())
        {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                cc.log('No local manifest file found, hot update skipped.');
                this.observer.onLoadFileFail();
                failed = true;
                break;
            case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                cc.log(event.getPercent());
                cc.log(event.getPercentByFile());

                cc.log(event.getDownloadedFiles() + ' / ' + event.getTotalFiles());
                cc.log(event.getDownloadedBytes() + ' / ' + event.getTotalBytes());

                var msg = event.getMessage();
                if (msg) {
                    cc.log('Updated file: ' + msg);
                    // cc.log(event.getPercent()/100 + '% : ' + msg);
                }
                // this.publish(HotUpdateEventName.UPDATE_STATE,event);
                this.observer.onUpdateProcess(event);
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                cc.log('Fail to download manifest file, hot update skipped.');
                this.observer.onLoadFileFail();
                failed = true;
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                cc.log('Already up to date with the latest remote version.');
                // this.publish(HotUpdateEventName.UPDATE_FINISH,event);
                this.observer.onUpdateFinish();
                failed = true;
                break;
            case jsb.EventAssetsManager.UPDATE_FINISHED:
                cc.log('Update finished. ' + event.getMessage());
                this.observer.onUpdateFinish();
                needRestart = true;
                break;
            case jsb.EventAssetsManager.UPDATE_FAILED:
                cc.log('Update failed. ' + event.getMessage());
                // this._updating = false;
                // this._canRetry = true;
                this.observer.onUpdateFail();
                break;
            case jsb.EventAssetsManager.ERROR_UPDATING:
                cc.log('Asset update error: ' + event.getAssetId() + ', ' + event.getMessage());
                this.observer.onUpdateFail();
                break;
            case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                cc.log(event.getMessage());
                this.observer.onUpdateFail();
                break;
            default:
                break;
        }

        if (failed) {
            this.onDestroy();
            // this._updateListener = null;
            // this._updating = false;
            cc.log('Update failed. ' + event.getMessage());
            this.publish(HotUpdateEventName.UPDATE_FAIL,event);
        }
        cc.log('Update needRestart. ' + needRestart);
        if (needRestart) {
            this.onDestroy();
            // this._updateListener = null;
            // Prepend the manifest's search path
            var searchPaths = jsb.fileUtils.getSearchPaths();
            var newPaths = this._am.getLocalManifest().getSearchPaths();
            console.log("newPaths ============= "+JSON.stringify(newPaths));
            if(searchPaths.indexOf(searchPaths) < 0){ // 防止重复添加，使搜索路径变得臃肿。
                Array.prototype.unshift.apply(searchPaths, newPaths);
                cc.log(" JSON.stringify(searchPaths) ========  "+JSON.stringify(searchPaths))
                cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
                jsb.fileUtils.setSearchPaths(searchPaths);                
            }
           
            // This value will be retrieved and appended to the default search path during game startup,
            // please refer to samples/js-tests/main.js for detailed usage.
            // !!! Re-add the search paths in main.js is very important, otherwise, new scripts won't take effect.

        }        


    } 

    restart(){
        cc.audioEngine.stopAll();
        cc.game.restart();
    }
    
    /**
     * 
     */
    retry() {
        // if (!this._updating && this._canRetry) {
            // this.panel.retryBtn.active = false;
            // this._canRetry = false;
            
            cc.log('Retry failed Assets...');
            this._am.downloadFailedAssets();
        // }
    }
    
    /**
     * 检查是否需要更新
     */
    checkUpdate() {
        // if (this._updating) {
        //     cc.log('Checking or updating ...');
        //     return;
        // }
        this.loadLocalManifest();
        if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) {
            cc.log('Failed to load local manifest ...');
            return;
        }
        this._am.setEventCallback(this.checkCb.bind(this));

        this._am.checkUpdate();
        // this._updating = true;
    }


    /**
     * 加载本地的对比文件，如果没有就创建一个。
     */
    loadLocalManifest(){
        if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
            if (jsb.fileUtils.isFileExist(this._localFile)){
                cc.log("loadLocalManifest 11111111111111 ")
                this._am.loadLocalManifest(this._localFile);
            }else{
                if(this.manifestUrl){
                    cc.log("loadLocalManifest 22222222222222 "+this.manifestUrl.nativeUrl)
                    var url = this.manifestUrl.nativeUrl;
                    if (cc.loader.md5Pipe) {
                        url = cc.loader.md5Pipe.transformURL(url);
                    }
                    cc.log("loadLocalManifest 3333333333 ")
                    this._am.loadLocalManifest(url);
                }else{
                    cc.log("loadLocalManifest 44444444444444 ");
                    var manifest = new jsb.Manifest(this.customManifestStr, this._storagePath);
                    this._am.loadLocalManifest(manifest, this._storagePath);
                }

            } 
        }
    }

    /**
     * 执行更新
     */
    hotUpdate() {
        if (this._am ) {
            this._am.setEventCallback(this.updateCb.bind(this));
            // this.loadLocalManifest();
            this._failCount = 0;
            this._am.update();
            // this.panel.updateBtn.active = false;
            // this._updating = true;
        }
    } 
}
